﻿using System;
using System.Collections.Generic;
using System.Linq;
using LinqToTwitter;
using Microsoft.SystemCenter.Orchestrator.Integration;
using SocialMediaIntegrationPack.Properties;
using searchFilter = System.Linq.Expressions.Expression<System.Func<LinqToTwitter.Status, bool>>;

namespace SocialMediaIntegrationPack
{
    [Activity("Get Tweets")]
    public class GetTweetAct : TwitterActivity
    {
        public GetTweetAct()
        {
        }

        /// <summary>
        /// Search filter, may have the following items:
        ///   #... - search by HashTag
        ///   @... - search by mentioned user
        ///   ...  - search by substring
        /// </summary>
        [ActivityInput("Filter", Optional = true)]
        public string Filter
        {
            set
            {
                if (string.IsNullOrEmpty(value)) return;

                if (value[0] == '#') {
                    ByHashTag = value.Substring(1);
                }
                else if (value[0] == '@') {
                    ByUserNameMention = value.Substring(1);
                }
                else {
                    BySubstring = value;
                }
            }
        }

        /// <summary>
        /// Amount of retry attempts in case of bad gateway error.
        /// </summary>
        [ActivityInput("Attempts",Default = "3")]
        public string Attempts { get; set; }

        
        /// <summary>
        /// Search for tweets only by this user
        ///  old: ByUserName
        /// </summary>
        [ActivityInput("Author", Optional = true)]
        public string Author { get; set; }

        /// <summary>
        /// Scope of tweets (obsolete)
        /// </summary>
        //[ActivityInput("Scope", Default = "Home", Options = "Home,Public")]
        public string Type
        {
            get { return type.ToString(); }
            set
            {
                if (string.IsNullOrEmpty(value)) 
                    throw new Exception(Resources.SPECIFY_STATUS_TYPE);
                
                if (value == "Public") {
                    type = StatusType.User;
                }
                else {
                    type = StatusType.Home;
                    TweetsMaximum = MaximumTweetsCount;
                }
            }
        }

        private StatusType type = StatusType.Home;

        [ActivityOutput("Tweets Found")]
        public int TweetsFound { get; set; }

        [ActivityOutput]
        public IEnumerable<TweetData> Tweets { get; set; }

        /// <summary>
        /// The maximum number of tweets
        /// </summary>
        protected int TweetsMaximum { get; set; }

        /// <summary>
        /// Search tweets containing substring
        /// </summary>
        protected string BySubstring { get; set; }

        /// <summary>
        /// Search tweets with HashTag
        /// </summary>
        protected string ByHashTag { get; set; }

        /// <summary>
        /// Search tweets mentioned this user
        /// </summary>
        protected string ByUserNameMention { get; set; }

        [ActivityMethod]
        public void Execute()
        {
            if (Configuration == null) {
                throw new ArgumentException(Resources.CONFIGURATION_MISSING);
            }

            try
            {
                var twitterContext = Authorize();
                var needEntities = (!string.IsNullOrEmpty(ByHashTag)) || (!string.IsNullOrEmpty(ByUserNameMention));

                var tweets = new List<TweetData>();

                int attempts = 0;
                if (!(Int32.TryParse(Attempts,out attempts)) || attempts <=0)
                {
                    throw new ArgumentException(Resources.INCORRECT_ATTEMPTS);
                }
                bool done = false;
                int _try = 1;
                do
                {
                    try
                    {
                        GetTweets(twitterContext, needEntities, TweetsMaximum)
                            .ForEach(tweet =>
                                tweets.Add(new TweetData(tweet.User.Identifier.ScreenName, tweet.Text, tweet.CreatedAt)));
                        done = true;
                    }
                    catch (TwitterQueryException ex)
                    {
                        if (ex.HttpError.Contains("502") || ex.HttpError.Contains("503") || ex.HttpError.Contains("504") || ex.HttpError.Contains("509"))
                        {
                            _try++;
                        }
                        else
                        {
                            throw;
                        }
                    }
                } while (!done && _try <= attempts);

                TweetsFound = tweets.Count;
                Tweets = tweets;
                Response = string.Format(Resources.GetTweetAct_Run_Found_tweets, tweets.Count);
            }
            catch (TwitterQueryException twitterQueryException)
            {
                Response = twitterQueryException.HttpError ?? twitterQueryException.Message;
                if (string.IsNullOrEmpty(twitterQueryException.Response.Error))
                {
                    Response += " Twitter reported error: " + twitterQueryException.Response.Error;
                }
                if (Response.Contains(Resources.GetTweetAct_Run_404))
                {
                    // Suppress 404 error cause it occurs every time when no tweets were found
                    TweetsFound = 0;
                    return;
                }

                if (twitterQueryException.InnerException != null)
                {
                    Response = twitterQueryException.InnerException.Message;
                }

                throw new Exception(Response);
            }
            catch (Exception exception)
            {
                LogException(exception);
                throw;
            }         
        }

        /// <summary>
        /// Load the tweets from twitter
        /// </summary>
        protected virtual List<Status> GetTweets(TwitterContext twitterContext, bool needEntities, int tweetCount)
        {
            //
            // Common expression for searching tweets
            //   - Set type
            //   - Set count
            //   - HashTag filter
            //   - Mentioned user filter
            //   - Substring filter
            //
            
            searchFilter commonExpression = t => t.Type == type
                                                   && t.Count == tweetCount
                                                   && (!needEntities || t.IncludeEntities)
                                                   && (string.IsNullOrEmpty(ByHashTag) || t.Entities.HashTagEntities.Any(tagObject => tagObject.Tag == ByHashTag))
                                                   && (string.IsNullOrEmpty(ByUserNameMention) || t.Entities.UserMentionEntities.Any(user => user.ScreenName == ByUserNameMention))
                                                   && (string.IsNullOrEmpty(BySubstring) || t.Text.Contains(BySubstring));

            var additionalFilters = GetAdditionalFilters();
            //
            // Select tweets
            //
            var tweets = (from tweet in twitterContext.Status
                              .Where(commonExpression)
                              .WhereAll(additionalFilters)
                          select tweet);

            // evaluation
            var list = tweets.ToList();
            AfterSearchComplete(tweets);

            return list;
        }

        //
        // Additional filters for searching tweets
        //
        protected virtual IEnumerable<searchFilter> GetAdditionalFilters()
        {
            searchFilter additionalExpression = tweet => tweet!=null;
            if (!string.IsNullOrEmpty(Author))
            {
                additionalExpression = tweet => tweet.User.Identifier.ScreenName == Author;
            }
            return new List<searchFilter> { additionalExpression };
        }

        /// <summary>
        /// Additional actions on result set
        /// </summary>
        protected virtual void AfterSearchComplete(IQueryable<Status> result)
        {
        }

        /// <summary>
        /// Maximum number of tweets chich can be found
        /// </summary>
        protected static readonly int MaximumTweetsCount = 200;
    }
}
